﻿using System;
using UnityEngine;

namespace CWM.Skinn
{
    [System.Serializable]
    public class BoneRenderer
    {
        public bool meshBuilt = false;
        public BoneMesh combinedMesh = new BoneMesh();
        public BoneRenderer() { }

        [Range(4, 10)]
        [SerializeField]
        private int resolution = 4;
        public int Resolution
        {
            get { resolution = Mathf.Clamp(resolution, 4, 30); return resolution; }
            set { resolution = value;}
        }

        [Range(0f, 1f)]
        [SerializeField]
        private float scaleStart = 0.5f;
        public float ScaleEnd { get { return scaleEnd; } set { scaleEnd = value; } }

        [Range(0f, 1f)]
        [SerializeField]
        private float scaleEnd = 0.001f;

        public float ScaleStart { get { return scaleStart; } set { scaleStart = value; } }

        [System.Serializable]
        public class BoneMesh
        {
            public BoneMesh() { }

            private Vector3[] vertices = new Vector3[0];
            private Color[] colors = new Color[0];
            private Vector2[] uvs = new Vector2[0];
            private int vertexIndex = 0;
            private int trisIndex = 0;

            private int[] indices = new int[0];
            private int[] indicesWire = new int[0];
            private int[] indicesSolid = new int[0];

            [SerializeField]
            private Mesh meshWire = null;
            public Mesh MeshWire
            {
                get { if (meshWire == null) { meshWire = new Mesh { name = "BoneMeshWire" }; meshWire.MarkDynamic(); } return meshWire; }
            }

            [SerializeField]
            private Mesh meshSolid = null;
            public Mesh MeshSolid
            {
                get { if (meshSolid == null) { meshSolid = new Mesh { name = "BoneMeshSolid" }; meshSolid.MarkDynamic(); } return meshSolid; }
            }

            public void Initialize(BoneRenderer boneRenderer, int count)
            {
                vertexIndex = 0;
                trisIndex = 0;
                vertices = new Vector3[count * boneRenderer.resolution * 4];
                colors = new Color[count * boneRenderer.resolution * 4];
                uvs = new Vector2[count * boneRenderer.resolution * 4];
                indices = new int[count * boneRenderer.resolution * 12];
                indicesWire = new int[count * boneRenderer.resolution * 12];
                indicesSolid = new int[count * boneRenderer.resolution * 12];
            }

            public void AddBone(BoneRenderer boneRenderer, Vector3 positionA, Vector3 positionB, Color color, float size, bool wire = true)
            {
                Quaternion rotation = positionB - positionA != Vector3.zero ? Quaternion.LookRotation(positionB - positionA, Vector3.up) : Quaternion.identity;
                float length = Vector3.Distance(positionA, positionB);
                int resolution = boneRenderer.Resolution;

                float radiusStart = size * boneRenderer.ScaleEnd;
                float radiusEnd = size * boneRenderer.ScaleStart;
                Color colorStart = color;
                Color colorEnd = color;

                int vertexCount = 4 * resolution;
                int trisCount = 12 * resolution;

                Vector3[] vertices = new Vector3[vertexCount];
                Color[] colors = new Color[vertexCount];
                Vector2[] uvs = new Vector2[vertexCount];

                for (int i = 0; i < resolution; i++)
                {
                    Vector3 pos = Vector3.zero;

                    float angle = 2 * Mathf.PI * i / resolution;
                    float angleSin = Mathf.Sin(angle);
                    float angleCos = Mathf.Cos(angle);

                    uvs[i] = new Vector2(1.0f * i / resolution, 1);
                    uvs[i + resolution] = new Vector2(1.0f * i / resolution, 0);
                    uvs[i + (resolution * 2)] = new Vector2(1 * i / (resolution * 2), 0.5f);
                    uvs[i + (resolution * 3)] = new Vector2(1 * i / (resolution * 3), 0.5f);

                    pos = new Vector3(positionA.x + radiusStart * angleCos, positionA.y + radiusStart * angleSin, positionA.z);
                    pos = (rotation * (pos - positionA) + positionA);
                    vertices[i] = pos;
                    colors[i] = colorStart;
                    //End
                    pos = new Vector3(positionA.x + radiusEnd * angleCos, positionA.y + radiusEnd * angleSin, length + positionA.z);
                    pos = (rotation * (pos - positionA) + positionA);
                    vertices[i + resolution] = pos;
                    colors[i + resolution] = colorEnd;
                    //Start Cap
                    pos = positionA;
                    pos = (rotation * (pos - positionA) + positionA);
                    vertices[i + resolution + resolution] = pos;
                    colors[i + resolution + resolution] = colorStart;
                    //End Cap
                    pos = positionA;
                    pos.z = pos.z + length;
                    pos = (rotation * (pos - positionA) + positionA);
                    vertices[i + resolution + resolution + resolution] = pos;
                    colors[i + resolution + resolution + resolution] = colorEnd;

                }
                int[] indices = boneRenderer.BoneIndices(vertexIndex);
                Array.Copy(vertices, 0, this.vertices, vertexIndex, vertexCount);
                Array.Copy(colors, 0, this.colors, vertexIndex, vertexCount);
                Array.Copy(uvs, 0, this.uvs, vertexIndex, vertexCount);
                Array.Copy(indices, 0, this.indices, trisIndex, trisCount);
                Array.Copy(wire ? indices : new int[trisCount], 0, indicesWire, trisIndex, trisCount);
                Array.Copy(!wire ? indices : new int[trisCount], 0, indicesSolid, trisIndex, trisCount);

                vertexIndex += vertexCount;
                trisIndex += trisCount;
            }

            public void BuildMeshes()
            {
                Skinn.SkinnEx.Release(meshWire);
                Skinn.SkinnEx.Release(meshSolid);

                Mesh meshW = MeshWire;
                meshW.Clear();
                meshW.vertices = vertices;
                meshW.triangles = indices;
                meshW.colors = colors;
                meshW.uv = uvs;
                meshW.SetIndices(indicesWire, MeshTopology.Lines, 0);
                Mesh meshS = MeshSolid;
                meshS.Clear();
                meshS.vertices = vertices;
                meshS.triangles = indices;
                meshS.colors = colors;
                meshS.uv = uvs;
                meshS.SetIndices(indicesSolid, MeshTopology.Triangles, 0);
                meshS.RecalculateNormals();
                meshW.normals = meshS.normals;
            }

            public void Draw(Material material)
            {
                var mat = material == null ? SkinnGizmos.OverlayMaterial : material;
                if (mat == null) return;
                if (!Skinn.SkinnEx.HasMinimumRequirements(meshWire) || !Skinn.SkinnEx.HasMinimumRequirements(meshSolid)) return;
                mat.SetPass(0);
                Graphics.DrawMeshNow(MeshWire, Matrix4x4.identity);
                Graphics.DrawMeshNow(MeshSolid, Matrix4x4.identity);
            }
        }

        public void AddBone(Vector3 positionA, Vector3 positionB, Color color, float size, bool wire = true)
        {
            if (meshBuilt) return;
            combinedMesh.AddBone(this, positionA, positionB, color, size, wire);
        }

        public void BuildMeshes()
        {
            if (meshBuilt) return;
            combinedMesh.BuildMeshes();
            //meshBuilt = true;
        }

        public void Draw(Material material = null)
        {
            combinedMesh.Draw(material);
        }

        private int[] BoneIndices(int baseIndex = 0)
        {
            int resolution = Resolution;
            int t = 0;
            int[] tris = new int[resolution * 12];

            for (int i = 0; i < resolution; i++)
            {
                int index = i + baseIndex;
                int plusOne = index + 1;

                if (plusOne == resolution)
                {
                    plusOne = 0;
                }

                tris[t++] = index;
                tris[t++] = plusOne;
                tris[t++] = index + resolution;

                tris[t++] = plusOne + resolution;
                tris[t++] = index + resolution;
                tris[t++] = plusOne;

                tris[t++] = index + resolution + resolution;
                tris[t++] = plusOne;
                tris[t++] = index;

                tris[t++] = index + resolution + resolution + resolution;
                tris[t++] = index + resolution;
                tris[t++] = plusOne + resolution;
            }
            return tris;
        }
    }
}
